package com.ctgu.redislock01.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ctgu.redislock01.entity.UsersEntity;
import com.ctgu.redislock01.mapper.UsersMapper;
import com.ctgu.redislock01.rabbitmq.RabbitConfig;
import com.ctgu.redislock01.request.RedisVO;
import com.ctgu.redislock01.service.UsersService;
import com.ctgu.redislock01.utils.ApiResult;
import com.ctgu.redislock01.utils.RedisCache;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery;

/**
* @author Alex2
* @description 针对表【t_users】的数据库操作Service实现
* @createDate 2025-03-03 17:22:23
*/
@Log4j2
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, UsersEntity>
    implements UsersService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4);
    private static final Integer delayMillis = 1;

    private static final String TAG = "UsersServiceImpl";
    private static final String REDIS_KEY_USER = "cache:user:";
    private static final String REDIS_KEY_USERS = "cache:users:list";
    private static final String LOCK_KEY_USER = "lock:user:";

    /**
     * @Author: Alex
     * @Description: 缓存列表
     */
    @Override
    public ApiResult getAllUser() {
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(REDIS_KEY_USERS);
        // 缓存命中，直接返回
        if (!entries.isEmpty()) {
            log.info("{} : Redis 缓存命中，返回用户列表", TAG);
            List<UsersEntity> userList = entries.values().stream()
                    .map(obj -> objectMapper.convertValue(obj, UsersEntity.class))
                    .collect(Collectors.toList());
            return ApiResult.success("缓存命中，获取用户列表成功", userList);
        }

        // 缓存未命中，查询数据库，有缓存穿透风险
        List<UsersEntity> usersEntityList = lambdaQuery().list();
        if (usersEntityList.isEmpty()) {
            log.error("{} : 数据库中无用户数据", TAG);
            return ApiResult.error("数据库中没有用户数据");
        }

        Map<String, UsersEntity> map = usersEntityList.stream()
                .collect(Collectors.toMap(user -> user.getId().toString(), user -> user));

        redisTemplate.opsForHash().putAll(REDIS_KEY_USERS, map);
        redisTemplate.expire(REDIS_KEY_USERS, Duration.ofMinutes(10));

        log.info("{} : Redis 缓存未命中，查询 MySQL 并存入缓存", TAG);
        return ApiResult.success("缓存未命中，获取用户列表成功", usersEntityList);
    }

    /**
     * @Author: Alex
     * @Description: 双写一致-延时双删
     */
    @Override
    public ApiResult updateUser(Integer id, String username, String password, String phone) {
        UsersEntity usersEntity = new UsersEntity(id, username, password, phone);
        // 更新数据库
        boolean updated = updateById(usersEntity);
        if (!updated) {
            log.error("{} : 更新失败，数据库未修改数据", TAG);
            return ApiResult.error("更新失败，数据库未修改数据");
        }
        // 删除缓存
        redisTemplate.delete(REDIS_KEY_USER + usersEntity.getId());
        // 延迟1s后执行第二次删除
        scheduledExecutor.schedule(() ->
                        redisTemplate.delete(REDIS_KEY_USER + usersEntity.getId()),
                delayMillis, TimeUnit.SECONDS
        );
        log.info("{} : 更新成功", TAG);
        return ApiResult.success("更新成功");
    }

    /**
     * @Author: Alex
     * @Description: 双写一致-RabbitMQ
     */
    @Override
    public ApiResult updateUsers(Integer id, String username, String password, String phone) {
        UsersEntity usersEntity = new UsersEntity(id, username, password, phone);
        // 更新数据库
        boolean updated = updateById(usersEntity);
        if (!updated) {
            log.error("{} : 更新失败，数据库未修改数据", TAG);
            return ApiResult.error("更新失败，数据库未修改数据");
        }
        log.info("{} : 发送异步消息，通知删除缓存", TAG);
        // 发送异步消息，通知删除缓存
        rabbitTemplate.convertAndSend(
                RabbitConfig.EXCHANGE_USER_UPDATE,
                RabbitConfig.ROUTING_KEY_USER_UPDATE,
                REDIS_KEY_USER + usersEntity.getId()
        );
        log.info("{} : 更新成功", TAG);
        return ApiResult.success("更新成功");
    }

    /**
     * @Author: Alex
     * @Description: 解决缓存穿透-缓存空对象
     */
    @Override
    public ApiResult getUser(Integer id) {
        Object object = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
        UsersEntity user = objectMapper.convertValue(object, UsersEntity.class);

        // Redis缓存未命中，查询数据库
        if (user == null) {
            UsersEntity usersEntity = lambdaQuery().eq(UsersEntity::getId, id).one();
            if (usersEntity == null) {      // mysql未命中，缓存空对象
                UsersEntity emptyEntity = new UsersEntity(-1, null, null, null);
                redisTemplate.opsForValue().set(REDIS_KEY_USER + id, emptyEntity, Duration.ofMinutes(1));
                log.info("{} : mysql未命中，缓存空对象", TAG);
                return ApiResult.error("mysql未命中，缓存空对象");
            } else {                        // mysql命中，缓存数据
                redisTemplate.opsForValue().set(REDIS_KEY_USER + id, usersEntity, Duration.ofMinutes(10));
                log.info("{} : mysql命中，缓存数据 : {}", TAG, usersEntity);
                return ApiResult.success("mysql命中，缓存数据", usersEntity);
            }
        }

        if (user.getId() == -1) {       // Redis缓存命中，该用户不存在，为空对象
            log.info("{} : Redis缓存命中，该用户不存在，为空对象, userId={}", TAG, id);
            return ApiResult.error("Redis缓存命中，该用户不存在，为空对象");
        } else {                        // Redis缓存命中，获取用户成功
            log.info("{} : Redis缓存命中，获取用户成功 : {}", TAG, user);
            return ApiResult.success("Redis缓存命中，获取用户成功", user);
        }
    }

    /**
     * @Author: Alex
     * @Description: 解决缓存雪崩-随机过期时间
     */
    @Override
    public ApiResult getUserEntity(Integer id) {
        Object object = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
        UsersEntity user = objectMapper.convertValue(object, UsersEntity.class);

        // Redis缓存未命中，查询数据库
        if (user == null) {
            UsersEntity usersEntity = lambdaQuery().eq(UsersEntity::getId, id).one();
            if (usersEntity == null) {      // mysql未命中，缓存空对象
                UsersEntity emptyEntity = new UsersEntity(-1, null, null, null);
                redisTemplate.opsForValue().set(REDIS_KEY_USER + id, emptyEntity, Duration.ofMinutes(1));
                log.info("{} : mysql未命中，缓存空对象", TAG);
                return ApiResult.error("mysql未命中，缓存空对象");
            } else {                        // mysql命中，缓存数据
                Duration ttl = Duration.ofMinutes(10 + ThreadLocalRandom.current().nextInt(5));
                redisTemplate.opsForValue().set(REDIS_KEY_USER + id, usersEntity, ttl);
                log.info("{} : mysql命中，缓存数据 : {}", TAG, usersEntity);
                return ApiResult.success("mysql命中，缓存数据", usersEntity);
            }
        }

        if (user.getId() == -1) {       // Redis缓存命中，该用户不存在，为空对象
            log.info("{} : Redis缓存命中，该用户不存在，为空对象, userId={}", TAG, id);
            return ApiResult.error("Redis缓存命中，该用户不存在，为空对象");
        } else {                        // Redis缓存命中，获取用户成功
            log.info("{} : Redis缓存命中，获取用户成功 : {}", TAG, user);
            return ApiResult.success("Redis缓存命中，获取用户成功", user);
        }
    }

    /**
     * @Author: Alex
     * @Description: 解决缓存雪崩-定期刷新热点缓存（缓存不过期未演示，直接不设置TTL）
     */
    // 每隔 5 分钟刷新热点用户缓存
    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void refreshHotUserCache() {
        List<Integer> hotUserIds = Arrays.asList(1, 2, 3); // 热点用户ID列表
        for (Integer userId : hotUserIds) {
            UsersEntity user = lambdaQuery().eq(UsersEntity::getId, userId).one();
            if (user != null) {
                redisTemplate.opsForValue().set(REDIS_KEY_USER + userId, user); // 刷新缓存
                log.info("刷新热点用户缓存，userId={}", userId);
            }
        }
    }

    /**
     * @Author: Alex
     * @Description: 解决缓存击穿-互斥锁 + TTL 随机过期
     */

    // 获取锁
    private boolean addLock(String lockKey, String lockValue) {
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30));
        return Boolean.TRUE.equals(lock);
    }

    // 释放锁的 Lua 脚本，确保删除时锁的持有者正确
    private void releaseLock(String lockKey, String lockValue) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
    }

    // 刷新缓存
    private void refreshUserCacheById(Integer id) {
        UsersEntity user = lambdaQuery().eq(UsersEntity::getId, id).one();
        if (user == null) {     // mysql未命中，缓存空对象
            UsersEntity emptyUser = new UsersEntity(-1, null, null, null);
            redisTemplate.opsForValue().set(REDIS_KEY_USER + id, emptyUser, Duration.ofMinutes(1));
            log.info("{} : mysql未命中，缓存空对象", TAG);
        } else {                // mysql命中，缓存数据
            Duration ttl = Duration.ofMinutes(10 + ThreadLocalRandom.current().nextInt(5));
            redisTemplate.opsForValue().set(REDIS_KEY_USER + id, user, ttl);
            log.info("{} : mysql命中，缓存数据", TAG);
        }
    }

    @Override
    public ApiResult fetchUserById(Integer id) {
        Object object = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
        UsersEntity user = objectMapper.convertValue(object, UsersEntity.class);

        // Redis缓存未命中，查询数据库
        if (user == null) {
            String lockValue = UUID.randomUUID().toString(); // 生成唯一锁值
            boolean lock = addLock(LOCK_KEY_USER + id, lockValue);

            try {
                if (lock) {      // 尝试获取锁，避免多个线程同时查询数据库
                    log.info("{} : 获取到锁，刷新缓存", TAG);
                    refreshUserCacheById(id);
                } else {        // 未获取到锁的线程短暂等待后重试，即自旋策略
                    for (int i = 0; i < 5; i++) {
                        Thread.sleep(50 * (i + 1));
                        Object cacheAgain = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
                        if (cacheAgain != null) {
                            UsersEntity retryUser = objectMapper.convertValue(cacheAgain, UsersEntity.class);
                            if (retryUser.getId() == -1) {
                                return ApiResult.error("该用户不存在");
                            }
                            return ApiResult.success("缓存命中（重试后）", retryUser);
                        }
                    }
                    return ApiResult.error("系统繁忙，请稍后重试");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("{} : 服务器异常，请稍后重试", TAG);
                return ApiResult.error("服务器异常，请稍后重试");
            } finally {
                if (lock) {     // 释放锁，使用 Lua 脚本确保原子性
                    releaseLock(LOCK_KEY_USER + id, lockValue);
                }
            }
        }

        if (user == null || user.getId() == -1) {   // Redis缓存命中，该用户不存在，为空对象
            log.info("{} : Redis缓存命中，该用户不存在，为空对象", TAG);
            return ApiResult.error("Redis缓存命中，该用户不存在，为空对象");
        } else {                    // Redis缓存命中，获取用户成功
            log.info("{} : Redis缓存命中，获取用户成功 : {}", TAG, user);
            return ApiResult.success("Redis缓存命中，获取用户成功", user);
        }
    }

    /**
     * @Author: Alex
     * @Description: 解决缓存击穿-互斥锁-逻辑过期 + 异步刷新
     */

    // 创建线程池
    private static final ExecutorService executor = Executors.newFixedThreadPool(10);

    // 判断缓存是否过期
    public Boolean isExpired(RedisVO redisVO) {
        if (redisVO == null || redisVO.getExpireTime() == null) {
            return true; // 如果对象或过期时间为空，视为已过期
        }
        return redisVO.getExpireTime().isBefore(LocalDateTime.now()); // 过期返回 true，未过期返回 false
    }

    // 缓存预热或刷新缓存
    public void refreshUserCache(Integer id) {
        UsersEntity user = lambdaQuery().eq(UsersEntity::getId, id).one();
        if (user == null) {     // mysql未命中，缓存空对象
            RedisVO emptyEntity = new RedisVO(-1, null, null, null, null);
            emptyEntity.setExpireTime(LocalDateTime.now().plusMinutes(1));  // 1分钟过期
            redisTemplate.opsForValue().set(REDIS_KEY_USER + id, emptyEntity);
            log.info("{} : mysql未命中，缓存空对象", TAG);
        } else {                // mysql命中，刷新缓存
            RedisVO redisVO = new RedisVO();
            BeanUtils.copyProperties(user, redisVO);
            redisVO.setExpireTime(LocalDateTime.now().plusMinutes(5)); // 5分钟过期
            redisTemplate.opsForValue().set(REDIS_KEY_USER + id, redisVO);
            log.info("{} : mysql命中，刷新缓存", TAG);
        }
    }

    @Override
    public ApiResult fetchUserEntity(Integer id) {
        Object object = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
        RedisVO redisVO = objectMapper.convertValue(object, RedisVO.class);

        // Redis缓存未命中，查询数据库
        if (object == null) {
            refreshUserCache(id);
        }

        // 缓存未过期，直接返回
        if (!isExpired(redisVO)) {      // Redis缓存命中，获取用户成功
            log.info("{} : Redis缓存命中，获取用户成功 : {}", TAG, redisVO);
            return ApiResult.success("Redis缓存命中，获取用户成功", redisVO);
        }

        // 缓存过期，尝试获取互斥锁
        String lockValue = UUID.randomUUID().toString();
        boolean lock = addLock(LOCK_KEY_USER + id, lockValue);

        if (lock) {
            // 双检锁：再次检查缓存，避免重复刷新
            Object cachedObject = redisTemplate.opsForValue().get(REDIS_KEY_USER + id);
            RedisVO cachedRedis = objectMapper.convertValue(cachedObject, RedisVO.class);
            if (!isExpired(cachedRedis)) {
                log.info("{} : Redis缓存命中，获取用户成功 : {}", TAG, cachedRedis);
                releaseLock(LOCK_KEY_USER + id, lockValue);             // 及时释放锁
                return ApiResult.success("Redis缓存命中，获取用户成功", cachedRedis);
            }

            // 缓存仍然过期，异步刷新缓存
            executor.submit(() -> {
                try {
                    refreshUserCache(id);
                } catch (Exception e) {
                    log.error("更新缓存失败", e);
                } finally {
                    releaseLock(LOCK_KEY_USER + id, lockValue); // 确保锁释放
                }
            });

            // 返回旧缓存，客户端看到的是过期数据，但正在刷新
            log.info("{} : 缓存已过期，正在异步刷新缓存", TAG);
            return ApiResult.success("缓存已过期，正在异步刷新缓存", redisVO);
        } else {
            // 获取锁失败，直接返回旧缓存，避免阻塞线程
            log.warn("{} : 未获取到锁，直接返回旧缓存", TAG);
            return ApiResult.success("未获取到锁，直接返回旧缓存", redisVO);
        }
    }

    /**
     * @Author: Alex
     * @Description: 使用 Redis 工具类
     */
    private UsersEntity getRedisUser(Integer id) {
        return lambdaQuery().eq(UsersEntity::getId, id).one();
    }

    @Override
    public ApiResult getRedisUtils(Integer id) {
        Function<Integer, UsersEntity> getRedisUser = this::getRedisUser;
        // 使用缓存穿透方法
//        UsersEntity usersEntity = redisCache.queryWithPassThrough(REDIS_KEY_USER, id, UsersEntity.class, getRedisUser, 10L, TimeUnit.MINUTES);
        // 使用逻辑过期方法
//        UsersEntity usersEntity = redisCache.queryWithLogicalExpire(REDIS_KEY_USER, id, UsersEntity.class, getRedisUser, 10L, TimeUnit.MINUTES);
        // 使用互斥锁方法
        UsersEntity usersEntity = redisCache.queryWithMutex(REDIS_KEY_USER, id, UsersEntity.class, getRedisUser, 10L, TimeUnit.MINUTES);
        if (usersEntity == null) {
            log.error("{} : 用户数据不存在", TAG);
            return ApiResult.error("用户数据不存在");
        }
        log.info("{} : Redis缓存命中 : {}", TAG, usersEntity);
        return ApiResult.success("缓存命中，获取用户成功", usersEntity);
    }
}